﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace AndersLiu.PE
{
    public static class EnumUtility
    {
        public static string GetEnumStrig(Enum e)
        {
            return string.Format("{0:X}h ({0})", e);
        }

        public static string GetMaskedEnumString(Enum e)
        {
            return FormatMaskedEnum(e, ": ", ", ", "; ");
        }

        public static string FormatMaskedEnum(Enum e,
            string separatorGroupValue,
            string separatorValues,
            string separatorGroups)
        {
            throw new NotImplementedException();
        }
    }

    public static class IntUtility
    {
        public static int Count1Bit(ulong n)
        {
            int count = 0;

            while (n != 0)
            {
                count++;
                n &= (n - 1);
            }
            return count;
        }

        public static uint GetCompressedIntSize(byte firstByte)
        {
            if ((firstByte & 0x80) == 0)
                return 1;
            else if ((firstByte & 0xC0) == 0x80)
                return 2;
            else if ((firstByte & 0xE0) == 0xC0)
                return 4;
            else
                throw new NotSupportedException();
        }

        public static byte[] CompressUInt(uint data)
        {
            if (data <= 0x7F)
            {
                var bytes = new byte[1];
                bytes[0] = (byte)data;
                return bytes;
            }
            else if (data <= 0x3FFF)
            {
                var bytes = new byte[2];
                bytes[0] = (byte)(((data & 0xFF00) >> 8) | 0x80);
                bytes[1] = (byte)(data & 0x00FF);
                return bytes;
            }
            else if (data <= 0x1FFFFFFF)
            {
                var bytes = new byte[4];
                bytes[0] = (byte)(((data & 0xFF000000) >> 24) | 0xC0);
                bytes[1] = (byte)((data & 0x00FF0000) >> 16);
                bytes[2] = (byte)((data & 0x0000FF00) >> 8);
                bytes[3] = (byte)(data & 0x000000FF);
                return bytes;
            }
            else
                throw new NotSupportedException();
        }

        public static uint DecompressUInt(byte[] data)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            if ((data[0] & 0x80 /* (1000000B) */) == 0  // 使用一个字节存储大小（0bbbbbbb B)   
                && data.Length == 1)
            {
                return (uint)data[0];
            }
            else if ((data[0] & 0xC0 /* (11000000B) */) == 0x80 /* (10000000B) */  // 使用两个字节存放大小（10bbbbbb bbbbbbbb B)   
                && data.Length == 2)
            {
                return (uint)((data[0] & 0x3F /* (00111111B) */) << 8 | data[1]);
            }
            else if ((data[0] & 0xE0 /* (11100000B) */) == 0xC0 /* (11000000B) */  // 使用四个字节存放大小（110bbbbb bbbbbbbb bbbbbbbb bbbbbbbb B）   
                && data.Length == 4)
            {
                return (uint)((data[0] & 0x1F /* (00011111B) */) << 24 | data[1] << 16 | data[2] << 8 | data[3]);
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        public static byte[] CompressInt(int data)
        {
            var u = data >= 0 ? (uint)data : ~(uint)data;
            if (u <= 0x3F)  // (u << 1) <= 0x7F
            {
                var uv = ((uint)data & 0x0000003F) << 1;
                if (data < 0)
                    uv |= 0x01;

                var bytes = new byte[1];
                bytes[0] = (byte)uv;
                return bytes;
            }
            else if (u <= 0x1FFF)
            {
                var uv = ((uint)data & 0x00001FFF) << 1;
                if (data < 0)
                    uv |= 0x01;

                var bytes = new byte[2];
                bytes[0] = (byte)(((uv & 0xFF00) >> 8) | 0x80);
                bytes[1] = (byte)(uv & 0x00FF);
                return bytes;
            }
            else if (u <= 0x0FFFFFFF)
            {
                var uv = ((uint)data & 0x0FFFFFFF) << 1;
                if (data < 0)
                    uv |= 0x01;

                var bytes = new byte[4];
                bytes[0] = (byte)(((uv & 0xFF000000) >> 24) | 0xC0);
                bytes[1] = (byte)((uv & 0x00FF0000) >> 16);
                bytes[2] = (byte)((uv & 0x0000FF00) >> 8);
                bytes[3] = (byte)(uv & 0x000000FF);
                return bytes;
            }
            else
                throw new NotSupportedException();
        }

        public static int DecompressInt(byte[] data)
        {
            var u = DecompressUInt(data);

            if ((u & 0x00000001) == 0)
                return (int)(u >> 1);

            var nb = GetCompressedIntSize(data[0]);
            uint sm;
            switch (nb)
            {
                case 1: sm = 0xFFFFFFC0; break;
                case 2: sm = 0xFFFFE000; break;
                case 4: sm = 0xF0000000; break;
                default: throw new NotSupportedException();
            }

            return (int)((u >> 1) | sm);
        }
    }
}
